Skip to contentMethod: copyChangeSet(ObjectChangeSet, Object, Object, Descriptor)
1: /**
2: * Copyright (C) 2022 Czech Technical University in Prague
3: * <p>
4: * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
5: * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
6: * version.
7: * <p>
8: * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9: * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10: * details. You should have received a copy of the GNU General Public License along with this program. If not, see
11: * <http://www.gnu.org/licenses/>.
12: */
13: package cz.cvut.kbss.jopa.sessions;
14:
15: import cz.cvut.kbss.jopa.adapters.IndirectCollection;
16: import cz.cvut.kbss.jopa.adapters.IndirectWrapper;
17: import cz.cvut.kbss.jopa.exceptions.EntityNotFoundException;
18: import cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException;
19: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
20: import cz.cvut.kbss.jopa.model.AbstractEntityManager;
21: import cz.cvut.kbss.jopa.model.BeanListenerAspect;
22: import cz.cvut.kbss.jopa.model.EntityManagerImpl.State;
23: import cz.cvut.kbss.jopa.model.LoadState;
24: import cz.cvut.kbss.jopa.model.MetamodelImpl;
25: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
26: import cz.cvut.kbss.jopa.model.lifecycle.PostLoadInvoker;
27: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
28: import cz.cvut.kbss.jopa.model.metamodel.EntityTypeImpl;
29: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
30: import cz.cvut.kbss.jopa.query.NamedQueryManager;
31: import cz.cvut.kbss.jopa.query.ResultSetMappingManager;
32: import cz.cvut.kbss.jopa.query.criteria.CriteriaBuilderImpl;
33: import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory;
34: import cz.cvut.kbss.jopa.sessions.change.ChangeManagerImpl;
35: import cz.cvut.kbss.jopa.sessions.change.ChangeRecordImpl;
36: import cz.cvut.kbss.jopa.sessions.change.ChangeSetFactory;
37: import cz.cvut.kbss.jopa.sessions.descriptor.InstanceDescriptor;
38: import cz.cvut.kbss.jopa.sessions.descriptor.InstanceDescriptorFactory;
39: import cz.cvut.kbss.jopa.sessions.validator.AttributeModificationValidator;
40: import cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator;
41: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
42: import cz.cvut.kbss.jopa.utils.Wrapper;
43: import org.aspectj.lang.Aspects;
44:
45: import java.lang.reflect.Field;
46: import java.net.URI;
47: import java.util.*;
48: import java.util.Map.Entry;
49: import java.util.function.Consumer;
50:
51: import static cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException.individualAlreadyManaged;
52: import static cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator.getValidator;
53: import static cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator.isNotInferred;
54: import static cz.cvut.kbss.jopa.utils.EntityPropertiesUtils.getValueAsURI;
55:
56: public class UnitOfWorkImpl extends AbstractSession implements UnitOfWork, ConfigurationHolder, Wrapper {
57:
58: // Read-only!!! It is just the keyset of cloneToOriginals
59: private final Set<Object> cloneMapping;
60: private final Map<Object, Object> cloneToOriginals;
61: private final Map<Object, Object> keysToClones = new HashMap<>();
62: private final Map<Object, Object> deletedObjects;
63: private final Map<Object, Object> newObjectsCloneToOriginal;
64: private final Map<Object, Object> newObjectsKeyToClone = new HashMap<>();
65: private final Map<Object, InstanceDescriptor> instanceDescriptors;
66: private RepositoryMap repoMap;
67:
68: private boolean hasChanges;
69: private boolean hasNew;
70: private boolean hasDeleted;
71: private boolean shouldReleaseAfterCommit;
72: private boolean shouldClearCacheAfterCommit;
73: private boolean useTransactionalOntology;
74:
75: private boolean isActive;
76: private boolean inCommit;
77:
78: private UnitOfWorkChangeSet uowChangeSet = ChangeSetFactory.createUoWChangeSet();
79:
80: private final AbstractSession parent;
81: private AbstractEntityManager entityManager;
82: private final ConnectionWrapper storage;
83:
84: private final MergeManager mergeManager;
85: private final CloneBuilder cloneBuilder;
86: private final ChangeManager changeManager;
87: private final SparqlQueryFactory queryFactory;
88: private final CriteriaBuilder criteriaFactory;
89: private final IndirectWrapperHelper indirectWrapperHelper;
90: /**
91: * This is a shortcut for the second level cache.
92: */
93: private final CacheManager cacheManager;
94:
95: public UnitOfWorkImpl(AbstractSession parent) {
96: super(parent.getConfiguration());
97: this.parent = Objects.requireNonNull(parent);
98: this.cloneToOriginals = createMap();
99: this.cloneMapping = cloneToOriginals.keySet();
100: this.deletedObjects = createMap();
101: this.newObjectsCloneToOriginal = createMap();
102: this.instanceDescriptors = new IdentityHashMap<>();
103: this.repoMap = new RepositoryMap();
104: this.cloneBuilder = new CloneBuilderImpl(this);
105: this.indirectWrapperHelper = new IndirectWrapperHelper(this);
106: this.cacheManager = parent.getLiveObjectCache();
107: this.storage = acquireConnection();
108: this.queryFactory = new SparqlQueryFactory(this, storage);
109: this.criteriaFactory = new CriteriaBuilderImpl(this);
110: this.mergeManager = new MergeManagerImpl(this);
111: this.changeManager = new ChangeManagerImpl(this);
112: this.useTransactionalOntology = true;
113: this.isActive = true;
114: }
115:
116: CloneBuilder getCloneBuilder() {
117: return cloneBuilder;
118: }
119:
120: @Override
121: public UnitOfWork acquireUnitOfWork() {
122: throw new UnsupportedOperationException("Nested UoWs are not supported.");
123: }
124:
125: @Override
126: protected ConnectionWrapper acquireConnection() {
127: final ConnectionWrapper conn = parent.acquireConnection();
128: conn.setUnitOfWork(this);
129: return conn;
130: }
131:
132: @Override
133: public <T> T readObject(Class<T> cls, Object identifier, Descriptor descriptor) {
134: Objects.requireNonNull(cls);
135: Objects.requireNonNull(identifier);
136: Objects.requireNonNull(descriptor);
137:
138: return readObjectInternal(cls, identifier, descriptor);
139: }
140:
141: private <T> T readObjectInternal(Class<T> cls, Object identifier, Descriptor descriptor) {
142: assert cls != null;
143: assert identifier != null;
144: assert descriptor != null;
145: T result = readManagedObject(cls, identifier, descriptor);
146: if (result != null) {
147: return result;
148: }
149: result = storage.find(new LoadingParameters<>(cls, getValueAsURI(identifier), descriptor));
150:
151: if (result == null) {
152: return null;
153: }
154: final Object clone = registerExistingObject(result, descriptor,
155: Collections.singletonList(new PostLoadInvoker(getMetamodel())));
156: checkForIndirectObjects(clone);
157: return cls.cast(clone);
158: }
159:
160: private <T> T readManagedObject(Class<T> cls, Object identifier, Descriptor descriptor) {
161: // First try to find the object among new uncommitted objects
162: Object result = newObjectsKeyToClone.get(identifier);
163: if (result != null && (isInRepository(descriptor, result))) {
164: // The result can be returned, since it is already registered in this UOW
165: return cls.cast(result);
166: }
167: // Object is already managed
168: return getManagedClone(cls, identifier, descriptor);
169: }
170:
171: private <T> T getManagedClone(Class<T> cls, Object identifier, Descriptor descriptor) {
172: if (!keysToClones.containsKey(identifier)) {
173: return null;
174: }
175: final Object clone = keysToClones.get(identifier);
176: if (!cls.isAssignableFrom(clone.getClass())) {
177: throw individualAlreadyManaged(identifier);
178: }
179: return isInRepository(descriptor, clone) && !deletedObjects.containsKey(clone) ? cls.cast(clone) : null;
180: }
181:
182: @Override
183: public <T> T getReference(Class<T> cls, Object identifier, Descriptor descriptor) {
184: Objects.requireNonNull(cls);
185: Objects.requireNonNull(identifier);
186: Objects.requireNonNull(descriptor);
187:
188: final T managedResult = readManagedObject(cls, identifier, descriptor);
189: if (managedResult != null) {
190: return managedResult;
191: }
192: final T result = storage.getReference(new LoadingParameters<>(cls, getValueAsURI(identifier), descriptor));
193: if (result == null) {
194: return null;
195: }
196: instanceDescriptors.put(result, InstanceDescriptorFactory.createNotLoaded(result, entityType(cls)));
197: registerEntityWithPersistenceContext(result);
198: registerEntityWithOntologyContext(result, descriptor);
199: if (getLiveObjectCache().contains(cls, identifier, descriptor)) {
200: cloneToOriginals.put(result, getLiveObjectCache().get(cls, identifier, descriptor));
201: } else {
202: cloneToOriginals.put(result, null);
203: }
204: keysToClones.put(identifier, result);
205: return result;
206: }
207:
208: /**
209: * This method calculates the changes that were to the registered entities and adds these changes into the given
210: * change set for future commit to the ontology.
211: */
212: private void calculateChanges() {
213: if (hasNew) {
214: calculateNewObjects(uowChangeSet);
215: }
216: if (hasDeleted) {
217: calculateDeletedObjects(uowChangeSet);
218: }
219: }
220:
221: /**
222: * Create object change sets for the new objects and adds them into our UnitOfWorkChangeSet.
223: *
224: * @param changeSet UnitOfWorkChangeSet
225: */
226: private void calculateNewObjects(UnitOfWorkChangeSet changeSet) {
227: for (Object clone : newObjectsCloneToOriginal.keySet()) {
228: final Descriptor c = getDescriptor(clone);
229: Object original = newObjectsCloneToOriginal
230: .computeIfAbsent(clone, key -> cloneBuilder.buildClone(key, new CloneConfiguration(c)));
231: if (original == null) {
232: throw new OWLPersistenceException(
233: "Error while calculating changes for new objects. Original not found.");
234: }
235: newObjectsCloneToOriginal.put(clone, original);
236: changeSet.addNewObjectChangeSet(ChangeSetFactory.createObjectChangeSet(original, clone,
237: c));
238: }
239: }
240:
241: private void calculateDeletedObjects(final UnitOfWorkChangeSet changeSet) {
242: for (Object clone : deletedObjects.keySet()) {
243: Descriptor descriptor = getDescriptor(clone);
244: changeSet.addDeletedObjectChangeSet(ChangeSetFactory.createDeleteObjectChangeSet(clone, descriptor));
245: changeSet.cancelObjectChanges(getOriginal(clone));
246: }
247: }
248:
249: @Override
250: public void clear() {
251: detachAllManagedInstances();
252: cloneToOriginals.clear();
253: keysToClones.clear();
254: deletedObjects.clear();
255: newObjectsCloneToOriginal.clear();
256: newObjectsKeyToClone.clear();
257: instanceDescriptors.clear();
258: this.hasChanges = false;
259: this.hasDeleted = false;
260: this.hasNew = false;
261: cloneBuilder.reset();
262: this.repoMap = new RepositoryMap();
263: repoMap.initDescriptors();
264: this.uowChangeSet = ChangeSetFactory.createUoWChangeSet();
265: }
266:
267: private void detachAllManagedInstances() {
268: cloneMapping.forEach(instance -> {
269: removeIndirectCollections(instance);
270: deregisterEntityFromPersistenceContext(instance);
271: });
272: newObjectsCloneToOriginal.keySet().forEach(instance -> {
273: removeIndirectCollections(instance);
274: deregisterEntityFromPersistenceContext(instance);
275: });
276: }
277:
278: @Override
279: public boolean contains(Object entity) {
280: Objects.requireNonNull(entity);
281: return isObjectManaged(entity);
282: }
283:
284: @Override
285: public void commit() {
286: LOG.trace("UnitOfWork commit started.");
287: if (!isActive()) {
288: throw new IllegalStateException("Cannot commit inactive Unit of Work!");
289: }
290: this.inCommit = true;
291: commitUnitOfWork();
292: LOG.trace("UnitOfWork commit finished.");
293: }
294:
295: @Override
296: public void rollback() {
297: LOG.trace("UnitOfWork rollback started.");
298: if (!isActive()) {
299: throw new IllegalStateException("Cannot rollback inactive Unit of Work!");
300: }
301: storage.rollback();
302: clear();
303: }
304:
305: /**
306: * Commit this Unit of Work.
307: */
308: private void commitUnitOfWork() {
309: commitToOntology();
310: mergeChangesIntoParent();
311: postCommit();
312: }
313:
314: /**
315: * Clean up after the commit.
316: */
317: private void postCommit() {
318: final boolean changes = hasChanges();
319: clear();
320: this.inCommit = false;
321: if (changes) {
322: if (shouldClearCacheAfterCommit) {
323: cacheManager.evictAll();
324: this.shouldReleaseAfterCommit = true;
325: } else {
326: cacheManager.evictInferredObjects();
327: }
328: }
329:
330: }
331:
332: /**
333: * If there are any changes, commit them to the ontology.
334: */
335: private void commitToOntology() {
336: if (this.hasNew || this.hasChanges || this.hasDeleted) {
337: calculateChanges();
338: }
339: validateIntegrityConstraints();
340: storageCommit();
341: }
342:
343: private void validateIntegrityConstraints() {
344: final IntegrityConstraintsValidator validator = getValidator();
345: for (ObjectChangeSet changeSet : uowChangeSet.getNewObjects()) {
346: validator.validate(changeSet.getCloneObject(), entityType((Class<Object>) changeSet.getObjectClass()), isNotInferred());
347: }
348: uowChangeSet.getExistingObjectsChanges().forEach(changeSet -> validator.validate(changeSet, getMetamodel()));
349: }
350:
351: private static Map<Object, Object> createMap() {
352: return new IdentityHashMap<>();
353: }
354:
355: /**
356: * Gets current state of the specified entity.
357: * <p>
358: * Note that since no repository is specified we can only determine if the entity is managed or removed. Therefore
359: * if the case is different this method returns State#NOT_MANAGED.
360: *
361: * @param entity The entity to check
362: * @return State of the entity
363: */
364: public State getState(Object entity) {
365: Objects.requireNonNull(entity);
366:
367: if (deletedObjects.containsKey(entity)) {
368: return State.REMOVED;
369: } else if (newObjectsCloneToOriginal.containsKey(entity)) {
370: return State.MANAGED_NEW;
371: } else if (cloneMapping.contains(entity)) {
372: return State.MANAGED;
373: } else {
374: return State.NOT_MANAGED;
375: }
376: }
377:
378: /**
379: * Checks the state of the specified entity with regards to the specified repository.
380: *
381: * @param entity Object
382: * @param descriptor Entity descriptor
383: * @return The state of the specified entity
384: */
385: public State getState(Object entity, Descriptor descriptor) {
386: Objects.requireNonNull(entity);
387: Objects.requireNonNull(descriptor);
388:
389: if (deletedObjects.containsKey(entity)) {
390: return State.REMOVED;
391: } else if (newObjectsCloneToOriginal.containsKey(entity) && isInRepository(descriptor, entity)) {
392: return State.MANAGED_NEW;
393: } else if (cloneMapping.contains(entity) && isInRepository(descriptor, entity)) {
394: return State.MANAGED;
395: } else {
396: return State.NOT_MANAGED;
397: }
398: }
399:
400: /**
401: * Tries to find the original object for the given clone. It searches the existing objects, new objects and deleted
402: * objects.
403: *
404: * @param clone Object
405: * @return The original object for the given clone
406: */
407: public Object getOriginal(Object clone) {
408: if (clone == null) {
409: return null;
410: }
411: return cloneToOriginals.containsKey(clone) ? cloneToOriginals.get(clone) : newObjectsCloneToOriginal.get(clone);
412: }
413:
414: /**
415: * Gets managed original with the specified identifier or {@code null} if there is none matching.
416: * <p>
417: * Descriptor is used to check repository context validity.
418: *
419: * @param cls Return type of the original
420: * @param identifier Instance identifier
421: * @param descriptor Repository descriptor
422: * @return Original object managed by this UoW or {@code null} if this UoW doesn't contain a matching instance
423: */
424: public <T> T getManagedOriginal(Class<T> cls, Object identifier, Descriptor descriptor) {
425: final T clone = getManagedClone(cls, identifier, descriptor);
426: return clone != null ? cls.cast(cloneToOriginals.get(clone)) : null;
427: }
428:
429: /**
430: * Check if this UnitOfWork contains this original entity. This method is used by the CloneBuilder so it does not
431: * have to clone already managed referenced objects.
432: *
433: * @param entity The original entity.
434: * @return True if the original is managed in this UnitOfWork.
435: */
436: boolean containsOriginal(Object entity) {
437: return entity != null && cloneToOriginals.containsValue(entity);
438: }
439:
440: /**
441: * Finds clone of the specified original.
442: *
443: * @param original The original object whose clone we are looking for
444: * @return The clone or null, if there is none
445: */
446: public Object getCloneForOriginal(Object original) {
447: for (Entry<Object, Object> entry : cloneToOriginals.entrySet()) {
448: // We use IdentityMap, so we can use ==
449: if (entry.getValue() == original) {
450: return entry.getKey();
451: }
452: }
453: return null;
454: }
455:
456: public boolean hasChanges() {
457: return hasChanges || hasDeleted || hasNew;
458: }
459:
460: void setHasChanges() {
461: this.hasChanges = true;
462: }
463:
464: @Override
465: public CacheManager getLiveObjectCache() {
466: return parent.getLiveObjectCache();
467: }
468:
469: UnitOfWorkChangeSet getUowChangeSet() {
470: return uowChangeSet;
471: }
472:
473: @Override
474: public boolean isActive() {
475: return this.isActive;
476: }
477:
478: /**
479: * Returns true if the given clone represents a newly created object. Otherwise returns false.
480: *
481: * @param clone Object
482: * @return boolean
483: */
484: public boolean isObjectNew(Object clone) {
485: return clone != null && newObjectsCloneToOriginal.containsKey(clone);
486: }
487:
488: /**
489: * Returns true if the given object is already managed.
490: *
491: * @param entity Object
492: * @return boolean
493: */
494: @Override
495: public boolean isObjectManaged(Object entity) {
496: Objects.requireNonNull(entity);
497:
498: return cloneMapping.contains(entity) && !deletedObjects.containsKey(entity) ||
499: newObjectsCloneToOriginal.containsKey(entity);
500: }
501:
502: /**
503: * Persists changed value of the specified field.
504: *
505: * @param entity Entity with changes (the clone)
506: * @param f The field whose value has changed
507: * @throws IllegalStateException If this UoW is not in transaction
508: */
509: public void attributeChanged(Object entity, Field f) {
510: if (!isInTransaction()) {
511: throw new IllegalStateException("This unit of work is not in a transaction.");
512: }
513: final Descriptor descriptor = getDescriptor(entity);
514: final EntityTypeImpl<Object> et = entityType((Class<Object>) entity.getClass());
515: et.getLifecycleListenerManager().invokePreUpdateCallbacks(entity);
516: storage.merge(entity, f, descriptor);
517: createAndRegisterChangeRecord(entity, et.getFieldSpecification(f.getName()), descriptor);
518: setHasChanges();
519: setIndirectObjectIfPresent(entity, f);
520: et.getLifecycleListenerManager().invokePostUpdateCallbacks(entity);
521: instanceDescriptors.get(entity).setLoaded(et.getFieldSpecification(f.getName()), LoadState.LOADED);
522: }
523:
524: private void createAndRegisterChangeRecord(Object clone, FieldSpecification<?, ?> fieldSpec,
525: Descriptor descriptor) {
526: final Object orig = getOriginal(clone);
527: if (orig == null) {
528: return;
529: }
530: final ChangeRecord record = new ChangeRecordImpl(fieldSpec,
531: EntityPropertiesUtils.getFieldValue(fieldSpec.getJavaField(), clone));
532: preventCachingIfReferenceIsNotLoaded(record);
533: registerChangeRecord(clone, orig, descriptor, record);
534: }
535:
536: private void preventCachingIfReferenceIsNotLoaded(ChangeRecord changeRecord) {
537: final Object newValue = changeRecord.getNewValue();
538: if (newValue != null && contains(newValue) && isLoaded(newValue) != LoadState.LOADED) {
539: changeRecord.preventCaching();
540: }
541: }
542:
543: private void registerChangeRecord(Object clone, Object orig, Descriptor descriptor, ChangeRecord record) {
544: ObjectChangeSet chSet = uowChangeSet.getExistingObjectChanges(orig);
545: if (chSet == null) {
546: chSet = ChangeSetFactory.createObjectChangeSet(orig, clone, descriptor);
547: uowChangeSet.addObjectChangeSet(chSet);
548: }
549: chSet.addChangeRecord(record);
550: }
551:
552: /**
553: * Merge the changes from this Unit of Work's change set into the server session.
554: */
555: private void mergeChangesIntoParent() {
556: if (hasChanges()) {
557: mergeManager.mergeChangesFromChangeSet(uowChangeSet);
558: }
559: evictPossiblyUpdatedReferencesFromCache();
560: }
561:
562: private void evictPossiblyUpdatedReferencesFromCache() {
563: cloneToOriginals.forEach((clone, orig) -> {
564: if (orig == null && !deletedObjects.containsKey(clone)) {
565: removeObjectFromCache(clone, getDescriptor(clone).getSingleContext().orElse(null));
566: }
567: });
568: }
569:
570: @Override
571: public <T> T mergeDetached(T entity, Descriptor descriptor) {
572: Objects.requireNonNull(entity);
573: Objects.requireNonNull(descriptor);
574:
575: final Object id = getIdentifier(entity);
576: if (!storage.contains(id, entity.getClass(), descriptor)) {
577: registerNewObject(entity, descriptor);
578: return entity;
579: } else {
580: if (isIndividualManaged(id, entity) && !isSameType(id, entity)) {
581: throw individualAlreadyManaged(id);
582: }
583: return mergeDetachedInternal(entity, descriptor);
584: }
585: }
586:
587: private boolean isSameType(Object id, Object entity) {
588: final Class<?> mergedType = entity.getClass();
589: final Object managed = keysToClones.containsKey(id) ? keysToClones.get(id) : newObjectsKeyToClone.get(id);
590: return managed != null && managed.getClass().isAssignableFrom(mergedType);
591: }
592:
593: private <T> T mergeDetachedInternal(T entity, Descriptor descriptor) {
594: assert entity != null;
595: final EntityTypeImpl<T> et = (EntityTypeImpl<T>) entityType(entity.getClass());
596: final URI idUri = EntityPropertiesUtils.getIdentifier(entity, et);
597:
598: final Object clone = getInstanceForMerge(idUri, et, descriptor);
599: try {
600: // Merge only the changed attributes
601: final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(clone, entity, descriptor);
602: changeManager.calculateChanges(chSet);
603: if (chSet.hasChanges()) {
604: et.getLifecycleListenerManager().invokePreUpdateCallbacks(clone);
605: final DetachedInstanceMerger merger = new DetachedInstanceMerger(this);
606: merger.mergeChangesFromDetachedToManagedInstance(chSet, descriptor);
607: for (ChangeRecord record : chSet.getChanges()) {
608: AttributeModificationValidator.verifyCanModify(record.getAttribute());
609: preventCachingIfReferenceIsNotLoaded(record);
610: final Field field = record.getAttribute().getJavaField();
611: storage.merge(clone, field, descriptor);
612: }
613: et.getLifecycleListenerManager().invokePostUpdateCallbacks(clone);
614: uowChangeSet.addObjectChangeSet(copyChangeSet(chSet, getOriginal(clone), clone, descriptor));
615: }
616: } catch (OWLEntityExistsException e) {
617: unregisterObject(clone);
618: throw e;
619: }
620: evictAfterMerge(et, idUri, descriptor);
621: setHasChanges();
622: checkForIndirectObjects(clone);
623: return et.getJavaType().cast(clone);
624: }
625:
626: private <T> Object getInstanceForMerge(URI identifier, EntityType<T> et, Descriptor descriptor) {
627: if (keysToClones.containsKey(identifier)) {
628: return keysToClones.get(identifier);
629: }
630: final LoadingParameters<T> params = new LoadingParameters<>(et.getJavaType(), identifier, descriptor, true);
631: T original = storage.find(params);
632: assert original != null;
633:
634: return registerExistingObject(original, descriptor);
635: }
636:
637: private static ObjectChangeSet copyChangeSet(ObjectChangeSet changeSet, Object original, Object clone,
638: Descriptor descriptor) {
639: final ObjectChangeSet newChangeSet = ChangeSetFactory.createObjectChangeSet(original, clone, descriptor);
640: changeSet.getChanges().forEach(newChangeSet::addChangeRecord);
641: return newChangeSet;
642: }
643:
644: private void evictAfterMerge(EntityType<?> et, URI identifier, Descriptor descriptor) {
645: if (cacheManager.contains(et.getJavaType(), identifier, descriptor)) {
646: cacheManager.evict(et.getJavaType(), identifier, descriptor.getSingleContext().orElse(null));
647: }
648: getMetamodel().getReferringTypes(et.getJavaType()).forEach(cacheManager::evict);
649: }
650:
651: private void registerEntityWithPersistenceContext(Object entity) {
652: Aspects.aspectOf(BeanListenerAspect.class).register(entity, this);
653: }
654:
655: private static void deregisterEntityFromPersistenceContext(Object entity) {
656: Aspects.aspectOf(BeanListenerAspect.class).deregister(entity);
657: }
658:
659: @Override
660: public NamedQueryManager getNamedQueryManager() {
661: return parent.getNamedQueryManager();
662: }
663:
664: @Override
665: public ResultSetMappingManager getResultSetMappingManager() {
666: return parent.getResultSetMappingManager();
667: }
668:
669: @Override
670: public Object registerExistingObject(Object entity, Descriptor descriptor) {
671: return registerExistingObject(entity, descriptor, Collections.emptyList());
672: }
673:
674: @Override
675: public Object registerExistingObject(Object entity, Descriptor descriptor, List<Consumer<Object>> postClone) {
676: if (entity == null) {
677: return null;
678: }
679: if (cloneToOriginals.containsValue(entity)) {
680: return getCloneForOriginal(entity);
681: }
682: final CloneConfiguration cloneConfig = new CloneConfiguration(descriptor);
683: postClone.forEach(cloneConfig::addPostRegisterHandler);
684: Object clone = cloneBuilder.buildClone(entity, cloneConfig);
685: assert clone != null;
686: registerClone(clone, entity, descriptor);
687: postClone.forEach(c -> c.accept(clone));
688: return clone;
689: }
690:
691: private void registerClone(Object clone, Object original, Descriptor descriptor) {
692: cloneToOriginals.put(clone, original);
693: final Object identifier = EntityPropertiesUtils.getIdentifier(clone, getMetamodel());
694: keysToClones.put(identifier, clone);
695: instanceDescriptors
696: .put(clone, InstanceDescriptorFactory.create(clone, (EntityType<Object>) entityType(clone.getClass())));
697: registerEntityWithPersistenceContext(clone);
698: registerEntityWithOntologyContext(clone, descriptor);
699: }
700:
701: /**
702: * Release this Unit of Work. Releasing an active Unit of Work with uncommitted changes causes all pending changes
703: * to be discarded.
704: */
705: @Override
706: public void release() {
707: clear();
708: storage.close();
709: this.isActive = false;
710: LOG.debug("UnitOfWork released.");
711: }
712:
713: @Override
714: public <T> void refreshObject(T object) {
715: Objects.requireNonNull(object);
716: if (!isObjectManaged(object)) {
717: throw new IllegalArgumentException(
718: "Cannot call refresh on an instance not managed by this persistence context.");
719: }
720: final EntityTypeImpl<T> et = entityType((Class<T>) object.getClass());
721: final URI idUri = EntityPropertiesUtils.getIdentifier(object, et);
722: final Descriptor descriptor = getDescriptor(object);
723:
724: final LoadingParameters<T> params = new LoadingParameters<>(et.getJavaType(), idUri, descriptor, true);
725: params.bypassCache();
726: final ConnectionWrapper connection = acquireConnection();
727: try {
728: uowChangeSet.cancelObjectChanges(getOriginal(object));
729: T original = connection.find(params);
730: if (original == null) {
731: throw new EntityNotFoundException("Entity " + object + " no longer exists in the repository.");
732: }
733: T source = (T) cloneBuilder.buildClone(original, new CloneConfiguration(descriptor));
734: final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(source, object, descriptor);
735: changeManager.calculateChanges(chSet);
736: new RefreshInstanceMerger(indirectWrapperHelper).mergeChanges(chSet);
737: revertTransactionalChanges(object, descriptor, chSet);
738: registerClone(object, original, descriptor);
739: et.getLifecycleListenerManager().invokePostLoadCallbacks(object);
740: } finally {
741: connection.close();
742: }
743: }
744:
745: private <T> void revertTransactionalChanges(T object, Descriptor descriptor, ObjectChangeSet chSet) {
746: for (ChangeRecord change : chSet.getChanges()) {
747: storage.merge(object, change.getAttribute().getJavaField(),
748: descriptor.getAttributeDescriptor(change.getAttribute()));
749: }
750: }
751:
752: @Override
753: public void registerNewObject(Object entity, Descriptor descriptor) {
754: Objects.requireNonNull(entity);
755: Objects.requireNonNull(descriptor);
756:
757: registerNewObjectInternal(entity, descriptor);
758: }
759:
760: /**
761: * Registers the specified entity for persist in this Unit of Work.
762: *
763: * @param entity The entity to register
764: * @param descriptor Entity descriptor, specifying optionally contexts into which the entity will be persisted
765: */
766: private void registerNewObjectInternal(Object entity, Descriptor descriptor) {
767: final EntityTypeImpl<?> eType = entityType(entity.getClass());
768: eType.getLifecycleListenerManager().invokePrePersistCallbacks(entity);
769: Object id = getIdentifier(entity);
770: if (id == null) {
771: EntityPropertiesUtils.verifyIdentifierIsGenerated(entity, eType);
772: }
773: verifyCanPersist(id, entity, eType, descriptor);
774: storage.persist(id, entity, descriptor);
775: if (id == null) {
776: // If the ID was null, extract it from the entity. It is present now
777: id = getIdentifier(entity);
778: }
779: assert id != null;
780: // Original is null until commit
781: newObjectsCloneToOriginal.put(entity, null);
782: registerEntityWithPersistenceContext(entity);
783: registerEntityWithOntologyContext(entity, descriptor);
784: instanceDescriptors.put(entity, InstanceDescriptorFactory.createAllLoaded(entity, (EntityType<Object>) eType));
785: newObjectsKeyToClone.put(id, entity);
786: checkForIndirectObjects(entity);
787: this.hasNew = true;
788: eType.getLifecycleListenerManager().invokePostPersistCallbacks(entity);
789: }
790:
791: private void verifyCanPersist(Object id, Object instance, EntityType<?> et, Descriptor descriptor) {
792: if (isIndividualManaged(id, instance) && !instance.getClass().isEnum()) {
793: throw individualAlreadyManaged(id);
794: }
795: if (storage.contains(id, instance.getClass(), descriptor)) {
796: throw new OWLEntityExistsException(
797: "Individual " + id + " of type " + et.getIRI() + " already exists in storage.");
798: }
799: }
800:
801: private boolean isIndividualManaged(Object identifier, Object entity) {
802: return keysToClones.containsKey(identifier) ||
803: newObjectsKeyToClone.containsKey(identifier) && !cloneMapping.contains(entity);
804: }
805:
806: @Override
807: public void removeObject(Object entity) {
808: assert entity != null;
809: if (!isObjectManaged(entity)) {
810: throw new IllegalArgumentException(
811: "Cannot remove entity which is not managed in the current persistence context.");
812: }
813: final EntityTypeImpl<?> et = entityType(entity.getClass());
814: et.getLifecycleListenerManager().invokePreRemoveCallbacks(entity);
815: final Object primaryKey = getIdentifier(entity);
816: final Descriptor descriptor = getDescriptor(entity);
817:
818: if (hasNew && newObjectsCloneToOriginal.containsKey(entity)) {
819: unregisterObject(entity);
820: newObjectsKeyToClone.remove(primaryKey);
821: } else {
822: deletedObjects.put(entity, entity);
823: this.hasDeleted = true;
824: }
825: storage.remove(primaryKey, et.getJavaType(), descriptor);
826: et.getLifecycleListenerManager().invokePostRemoveCallbacks(entity);
827: }
828:
829: @Override
830: public void restoreRemovedObject(Object entity) {
831: assert deletedObjects.containsKey(entity);
832:
833: deletedObjects.remove(entity);
834: final Object id = getIdentifier(entity);
835: storage.persist(id, entity, getDescriptor(entity));
836: }
837:
838: /**
839: * Remove the registered object from this Unit of Work.
840: *
841: * @param object Clone of the original object
842: */
843: public void unregisterObject(Object object) {
844: if (object == null) {
845: return;
846: }
847: final Object original = cloneToOriginals.remove(object);
848: keysToClones.remove(EntityPropertiesUtils.getIdentifier(object, getMetamodel()));
849:
850: deletedObjects.remove(object);
851: if (hasNew) {
852: newObjectsCloneToOriginal.remove(object);
853: }
854: if (original != null) {
855: cloneBuilder.removeVisited(original, repoMap.getEntityDescriptor(object));
856: }
857: removeIndirectCollections(object);
858: deregisterEntityFromPersistenceContext(object);
859: unregisterEntityFromOntologyContext(object);
860: }
861:
862: @Override
863: public boolean shouldReleaseAfterCommit() {
864: return shouldReleaseAfterCommit;
865: }
866:
867: public void setShouldClearAfterCommit(boolean shouldClearCache) {
868: this.shouldClearCacheAfterCommit = shouldClearCache;
869: }
870:
871: public void setEntityManager(AbstractEntityManager entityManager) {
872: this.entityManager = entityManager;
873: }
874:
875: @Override
876: public void writeUncommittedChanges() {
877: if (hasChanges()) {
878: commitUnitOfWork();
879: }
880: }
881:
882: @Override
883: public MetamodelImpl getMetamodel() {
884: return parent.getMetamodel();
885: }
886:
887: private <T> EntityTypeImpl<T> entityType(Class<T> cls) {
888: return getMetamodel().entity(cls);
889: }
890:
891: @Override
892: public boolean isEntityType(Class<?> cls) {
893: return parent.isEntityType(cls);
894: }
895:
896: @Override
897: public boolean isInTransaction() {
898: return entityManager != null && entityManager.getTransaction().isActive();
899: }
900:
901: /**
902: * Returns {@code true} if this UoW is currently committing changes.
903: *
904: * @return Whether this UoW is in the commit phase
905: */
906: public boolean isInCommit() {
907: return inCommit;
908: }
909:
910: @Override
911: public <T> void loadEntityField(T entity, Field field) {
912: Objects.requireNonNull(entity);
913: Objects.requireNonNull(field);
914: assert field.getDeclaringClass().isAssignableFrom(entity.getClass());
915:
916: final Descriptor entityDescriptor = getDescriptor(entity);
917: if (!instanceDescriptors.containsKey(entity)) {
918: throw new OWLPersistenceException(
919: "Unable to find repository identifier for entity " + entity + ". Is it managed by this UoW?");
920: }
921: final InstanceDescriptor<?> instanceDescriptor = instanceDescriptors.get(entity);
922: final FieldSpecification<?, ?> fieldSpec = entityType((Class<Object>) entity.getClass())
923: .getFieldSpecification(field.getName());
924: if (instanceDescriptor.isLoaded(fieldSpec) == LoadState.LOADED) {
925: return;
926: }
927:
928: storage.loadFieldValue(entity, field, entityDescriptor);
929: final Object orig = EntityPropertiesUtils.getFieldValue(field, entity);
930: final Object entityOriginal = getOriginal(entity);
931: if (entityOriginal != null) {
932: EntityPropertiesUtils.setFieldValue(field, entityOriginal, orig);
933: }
934: final Descriptor fieldDescriptor = getFieldDescriptor(entity, field, entityDescriptor);
935: final Object clone = cloneLoadedFieldValue(entity, field, fieldDescriptor, orig);
936: EntityPropertiesUtils.setFieldValue(field, entity, clone);
937: instanceDescriptors.get(entity).setLoaded(fieldSpec, LoadState.LOADED);
938: }
939:
940: private <T> Descriptor getFieldDescriptor(T entity, Field field, Descriptor entityDescriptor) {
941: final EntityType<?> et = entityType(entity.getClass());
942: final FieldSpecification<?, ?> fieldSpec = et.getFieldSpecification(field.getName());
943: return entityDescriptor.getAttributeDescriptor(fieldSpec);
944: }
945:
946: private <T> Object cloneLoadedFieldValue(T entity, Field field, final Descriptor fieldDescriptor,
947: final Object fieldValueOrig) {
948: Object clone;
949: if (fieldValueOrig == null) {
950: clone = null;
951: } else {
952: if (isEntityType(field.getType())) {
953: clone = registerExistingObject(fieldValueOrig, fieldDescriptor);
954: putObjectIntoCache(getIdentifier(clone), fieldValueOrig, fieldDescriptor);
955: } else {
956: clone = cloneBuilder.buildClone(entity, field, fieldValueOrig, fieldDescriptor);
957: }
958: }
959: return clone;
960: }
961:
962: @Override
963: public void removeObjectFromCache(Object toRemove, URI context) {
964: Objects.requireNonNull(toRemove);
965:
966: cacheManager.evict(toRemove.getClass(), getIdentifier(toRemove), context);
967: }
968:
969: @Override
970: public boolean isConsistent(URI context) {
971: return storage.isConsistent(context);
972: }
973:
974: @Override
975: public List<URI> getContexts() {
976: return storage.getContexts();
977: }
978:
979: @Override
980: public LoadState isLoaded(Object entity, String attributeName) {
981: Objects.requireNonNull(entity);
982: final FieldSpecification<?, ?> fs = entityType(entity.getClass()).getFieldSpecification(attributeName);
983: return instanceDescriptors.containsKey(entity) ? instanceDescriptors.get(entity).isLoaded(fs) :
984: LoadState.UNKNOWN;
985: }
986:
987: @Override
988: public LoadState isLoaded(Object entity) {
989: Objects.requireNonNull(entity);
990: return instanceDescriptors.containsKey(entity) ? instanceDescriptors.get(entity).isLoaded() : LoadState.UNKNOWN;
991: }
992:
993: @Override
994: public void setUseTransactionalOntologyForQueryProcessing() {
995: this.useTransactionalOntology = true;
996: }
997:
998: @Override
999: public boolean useTransactionalOntologyForQueryProcessing() {
1000: return useTransactionalOntology;
1001: }
1002:
1003: @Override
1004: public void setUseBackupOntologyForQueryProcessing() {
1005: this.useTransactionalOntology = false;
1006: }
1007:
1008: @Override
1009: public boolean useBackupOntologyForQueryProcessing() {
1010: return !useTransactionalOntology;
1011: }
1012:
1013: public SparqlQueryFactory sparqlQueryFactory() {
1014: return queryFactory;
1015: }
1016:
1017: public CriteriaBuilder criteriaFactory() {
1018: return criteriaFactory;
1019: }
1020:
1021: /**
1022: * Check if the specified entity contains a collection. If so, replace it with its indirect representation so that
1023: * changes in that collection can be tracked.
1024: *
1025: * @param entity The entity to check
1026: */
1027: private void checkForIndirectObjects(Object entity) {
1028: assert entity != null;
1029: final EntityType<?> et = entityType(entity.getClass());
1030: for (FieldSpecification<?, ?> fieldSpec : et.getFieldSpecifications()) {
1031: setIndirectObjectIfPresent(entity, fieldSpec.getJavaField());
1032: }
1033: }
1034:
1035: /**
1036: * Create and set indirect collection on the specified entity field.
1037: * <p>
1038: * If the specified field is of Collection type and it is not already an indirect collection, create new one and set
1039: * it as the value of the specified field on the specified entity.
1040: *
1041: * @param entity The entity collection will be set on
1042: * @param field The field to set
1043: * @throws IllegalArgumentException Reflection
1044: */
1045: private void setIndirectObjectIfPresent(Object entity, Field field) {
1046: assert entity != null;
1047: assert field != null;
1048:
1049: final Object value = EntityPropertiesUtils.getFieldValue(field, entity);
1050: if (value instanceof IndirectWrapper) {
1051: return;
1052: }
1053: if (IndirectWrapperHelper.requiresIndirectWrapper(value)) {
1054: EntityPropertiesUtils
1055: .setFieldValue(field, entity, indirectWrapperHelper.createIndirectWrapper(value, entity, field));
1056: }
1057: }
1058:
1059: /**
1060: * Creates an indirect collection, which wraps the specified collection instance and propagates changes to the
1061: * persistence context.
1062: *
1063: * @param collection Collection to be proxied
1064: * @param owner Collection owner instance
1065: * @param field Field filled with the collection
1066: * @return Indirect collection
1067: */
1068: public Object createIndirectCollection(Object collection, Object owner, Field field) {
1069: return indirectWrapperHelper.createIndirectWrapper(collection, owner, field);
1070: }
1071:
1072: /**
1073: * Remove indirect collection implementations from the specified entity (if present).
1074: *
1075: * @param entity The entity to remove indirect collections from
1076: */
1077: private void removeIndirectCollections(Object entity) {
1078: assert entity != null;
1079: final EntityType<?> et = entityType(entity.getClass());
1080: for (FieldSpecification<?, ?> fs : et.getFieldSpecifications()) {
1081: final Object value = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), entity);
1082: if (value instanceof IndirectCollection) {
1083: IndirectCollection<?> indCol = (IndirectCollection<?>) value;
1084: EntityPropertiesUtils.setFieldValue(fs.getJavaField(), entity, indCol.unwrap());
1085: }
1086: }
1087: }
1088:
1089: void putObjectIntoCache(Object identifier, Object entity, Descriptor descriptor) {
1090: cacheManager.add(identifier, entity, descriptor);
1091: }
1092:
1093: private Object getIdentifier(Object entity) {
1094: return EntityPropertiesUtils.getIdentifier(entity, getMetamodel());
1095: }
1096:
1097: private void unregisterEntityFromOntologyContext(Object entity) {
1098: assert entity != null;
1099:
1100: final Descriptor descriptor = repoMap.getEntityDescriptor(entity);
1101: if (descriptor == null) {
1102: throw new OWLPersistenceException("Fatal error, unable to find descriptor for entity " + entity);
1103: }
1104:
1105: repoMap.remove(descriptor, entity);
1106: repoMap.removeEntityToRepository(entity);
1107: }
1108:
1109: private void registerEntityWithOntologyContext(Object entity, Descriptor descriptor) {
1110: assert descriptor != null;
1111: assert entity != null;
1112:
1113: repoMap.add(descriptor, entity, null);
1114: repoMap.addEntityToRepository(entity, descriptor);
1115: }
1116:
1117: private boolean isInRepository(Descriptor descriptor, Object entity) {
1118: assert descriptor != null;
1119: assert entity != null;
1120:
1121: return repoMap.contains(descriptor, entity);
1122: }
1123:
1124: private Descriptor getDescriptor(Object entity) {
1125: assert entity != null;
1126:
1127: final Descriptor descriptor = repoMap.getEntityDescriptor(entity);
1128: if (descriptor == null) {
1129: throw new OWLPersistenceException("Unable to find descriptor of entity " + entity + " in this UoW!");
1130: }
1131: return descriptor;
1132: }
1133:
1134: private void storageCommit() {
1135: try {
1136: storage.commit();
1137: } catch (OWLPersistenceException e) {
1138: entityManager.removeCurrentPersistenceContext();
1139: throw e;
1140: }
1141: }
1142:
1143: @Override
1144: public <T> T unwrap(Class<T> cls) {
1145: if (cls.isAssignableFrom(getClass())) {
1146: return cls.cast(this);
1147: }
1148: return storage.unwrap(cls);
1149: }
1150:
1151: public SparqlQueryFactory getQueryFactory() {
1152: return queryFactory;
1153: }
1154: }